﻿using System;
using System.IO;
using System.Xml;
using System.Collections.Generic;
using System.Collections;

namespace XamlCleaner.Core.Pharser
{
    public class XamlPharser : IXamlPharser, IDisposable
    {
        #region Members
        XmlDocument xmlDoc;
        XmlDocument xmlOriginalDocument;
        XmlElement xmlRoot;
        List<XmlAttribute> listNameSpaces = new List<XmlAttribute>();

        #endregion

        #region Constructors

        public XamlPharser(XmlDocument xmlDocument)
        {
            if (xmlDocument == null)
                throw new ArgumentNullException("xmlDocument", "xmlDocument is null.");
            xmlDoc = xmlDocument;

            //
            // Clone the document to keep original copy.
            xmlOriginalDocument = (XmlDocument)xmlDocument.Clone();
        }

        #endregion

        #region IXamlPharser Methods

        #region Events
        public event ReplacingDuplicatedNameSpaceListEventHandler ReplacingDuplicatedNameSpaceList;

        public event ReplacedDuplicatedNameSpaceListEventHandler ReplacedDuplicatedNameSpaceList;

        public event ReplacingDuplicatedNameSpaceEventHandler ReplacingDuplicatedNameSpace;

        public event ReplacedDuplicatedNameSpaceEventHandler ReplacedDuplicatedNameSpace;

        #endregion

        public XmlDocument Pharse()
        {
            this.listNameSpaces = GetNameSpacesList(this.xmlDoc);

            //
            // If there are nameSpaces in xaml file.
            if (this.listNameSpaces.Count > 0)
            {
                DeleteOrphandNameSpace(this.listNameSpaces, this.xmlDoc);
                this.listNameSpaces = GetNameSpacesList(this.xmlDoc);
                List<XmlAttribute> cleanNameSpaces = ResolveDuplicatedNameSpace(this.listNameSpaces, this.xmlDoc);
                ReplaceNamSpaces(cleanNameSpaces, this.xmlDoc);
            }


            return this.xmlDoc;
        }

        public XmlDocument OriginalDocument
        {
            get
            {
                return this.OriginalDocument;
            }
        }

        #endregion

        #region Private Methods
        /// <summary> 
        /// Delete orphands name spaces.
        /// </summary>
        /// <param name="list">List of Name Spaces to find.</param>
        /// <param name="xmlDocument">Document to find NameSpaces</param>
        private void DeleteOrphandNameSpace(List<XmlAttribute> list, XmlDocument xmlDocument)
        {
            XmlNode rootNode = xmlDocument.FirstChild;
            foreach (XmlAttribute nameSpace in list)
            {
                if (!IsNameSpaceInNode(nameSpace, rootNode))
                {
                    rootNode.Attributes.Remove(nameSpace);
                }
            }
        }


        /// <summary>
        /// Looking for nameSpace in Xmlnode recursively
        /// </summary>
        /// <param name="nameSpace">nameSpace to find</param>
        /// <param name="node">Node</param>
        /// <returns>An boolean to indicate if namespace is in node.</returns>
        private bool IsNameSpaceInNode(XmlAttribute nameSpace, XmlNode node)
        {
            bool res = false;

            if (node.Prefix.Equals(nameSpace.LocalName))
                res = true;

            if (node.Attributes != null && !res)
            {
                foreach (XmlAttribute att in node.Attributes)
                {
                    if (att.Value.Contains(nameSpace.LocalName))
                    {
                        res = true;
                    }
                }
            }
            if (!res)
            {
                foreach (XmlNode item in node.ChildNodes)
                {
                    res |= IsNameSpaceInNode(nameSpace, item);
                }
            }
            return res;
        }


        private List<XmlAttribute> GetNameSpacesList(XmlDocument document)
        {
            if (document == null)
                throw new ArgumentNullException("xmlDocument", "xmlDocument is null.");

            List<XmlAttribute> res = new List<XmlAttribute>();

            xmlRoot = document.DocumentElement;
            XmlAttributeCollection nameSpaces = xmlRoot.Attributes;
            foreach (XmlAttribute att in nameSpaces)
            {
                if (att.Prefix.Equals("xmlns")
                    && !att.Name.Equals("xmlns:x"))
                {
                    res.Add(att);
                }
            }
            return res;
        }


        private List<XmlAttribute> ResolveDuplicatedNameSpace(List<XmlAttribute> nameSpaces, XmlDocument document)
        {
            if (nameSpaces == null)
                throw new ArgumentNullException("nameSpaces", "nameSpaces is null.");
            if (document == null)
                throw new ArgumentNullException("xmlDocument", "xmlDocument is null.");
            List<XmlAttribute> duplicatedList = new List<XmlAttribute>();
            List<XmlAttribute> mainNameSpaces = new List<XmlAttribute>();

            //
            // Generate duplicated name spaces and main NameSpace list.
            foreach (XmlAttribute nameSpace in nameSpaces)
            {
                //
                // If NameSpace exist in mainNameSpaces
                if (mainNameSpaces.FindIndex(n => n.Value.Equals(nameSpace.Value)) >= 0)
                {
                    duplicatedList.Add(nameSpace);
                }
                else //Add to mainNameSpace
                {
                    mainNameSpaces.Add(nameSpace);
                }
            }

            ReplaceDuplicates(duplicatedList, mainNameSpaces, document);
            return mainNameSpaces;
        }

        private void ReplaceDuplicates(List<XmlAttribute> duplicatedList, List<XmlAttribute> mainNameSpaces, XmlDocument document)
        {
            if (duplicatedList == null)
                throw new ArgumentNullException("duplicatedDict", "duplicatedDict is null.");
            if (mainNameSpaces == null)
                throw new ArgumentNullException("mainNameSpaces", "mainNameSpaces is null.");

            // Generate DuplicateList
            foreach (XmlAttribute item in mainNameSpaces)
            {
                //TODO: Add event
                ReplaceDuplicatedItem(duplicatedList, item, document);
                //TODO: Add Event
            }

        }

        private void ReplaceDuplicatedItem(List<XmlAttribute> duplicatedList, XmlAttribute item, XmlDocument document)
        {
            DuplicatedNameSpace dupNameSpace = new DuplicatedNameSpace(item);
            List<XmlAttribute> forRemoveNS = duplicatedList.FindAll(i => i.Value.Equals(item.Value));

            if (forRemoveNS != null)
            {
                dupNameSpace.AddDuplicatedNameSpace(forRemoveNS);

                ReplacingDuplicatedNameSpaceEventArgs e = new ReplacingDuplicatedNameSpaceEventArgs(dupNameSpace);
                if (!OnCancelReplacingDuplicatedNameSpace(e))
                {
                    foreach (string dup in e.DuplicatedNameSpace.DuplicatedNameSpaces)
                    {
                        document.DocumentElement.InnerXml = document.DocumentElement.InnerXml.Replace(dup, item.LocalName);
                    }
                }
                OnReplacedDuplicatedNameSpace(new ReplacedDuplicatedNameSpaceEventArgs(dupNameSpace));
            }
        }

        /// <summary>
        /// Replace old NameSpaces with new ones
        /// </summary>
        /// <param name="cleanNameSpaces">List of clean NameSpaces</param>
        /// <param name="document">Documento to replace NameSpaces.</param>
        private void ReplaceNamSpaces(List<XmlAttribute> cleanNameSpaces, XmlDocument document)
        {
            XmlElement root = document.DocumentElement;
            XmlAttributeCollection nameSpaces = root.Attributes;

            XmlNode classAtt = root.GetAttributeNode("x:Class").Clone();
            XmlNode xAtt = root.GetAttributeNode("xmlns:x").Clone();
            XmlNode emptyAtt = root.GetAttributeNode("xmlns").Clone();

            List<XmlAttribute> toRemove = new List<XmlAttribute>();
            foreach (XmlAttribute item in nameSpaces)
            {
                if (item.Prefix.Equals("xmlns")
                    || item.LocalName.Equals("xmlns"))
                    toRemove.Add(item);
            }

            root.RemoveAttribute("x:Class");
            foreach (XmlAttribute item in toRemove)
            {

                root.RemoveAttributeNode(item);
            }


            foreach (XmlAttribute item in cleanNameSpaces)
            {
                root.Attributes.Prepend(item);                
            }

            root.Attributes.Prepend((XmlAttribute)xAtt);
            root.Attributes.Prepend((XmlAttribute)emptyAtt);
            root.Attributes.Prepend((XmlAttribute)classAtt);
        }

        #region Events Raising Protected Methods

        /// <summary>
        /// Protected method for ReplacingDuplicatedNameSpaceListEventHandler event
        /// </summary>
        /// <param name="e">ReplacingDuplicatedNameSpaceListEventArgs parameter</param>
        /// <returns>e.Cancel property of CancelEventArgs</returns>
        private bool OnCancelReplacingDuplicatedNameSpaceList(ReplacingDuplicatedNameSpaceListEventArgs e)
        {
            if (this.ReplacingDuplicatedNameSpaceList != null)
                this.ReplacingDuplicatedNameSpaceList(this, e);
            return e.Cancel;
        }

        /// <summary>
        /// Protected method for ReplacingDuplicatedNameSpaceEventHandler event
        /// </summary>
        /// <param name="e">ReplacingDuplicatedNameSpaceEventArgs parameter</param>
        /// <returns>e.Cancel property of CancelEventArgs</returns>
        private bool OnCancelReplacingDuplicatedNameSpace(ReplacingDuplicatedNameSpaceEventArgs e)
        {
            if (this.ReplacingDuplicatedNameSpace != null)
                this.ReplacingDuplicatedNameSpace(this, e);
            return e.Cancel;
        }

        /// <summary>
        /// Protected method for ReplacedDuplicatedNameSpaceListEventHandler event
        /// </summary>
        /// <param name="e">ReplacedDuplicatedNameSpaceListEventArgs parameter</param>
        private void OnReplacedDuplicatedNameSpaceList(ReplacedDuplicatedNameSpaceListEventArgs e)
        {
            if (this.ReplacedDuplicatedNameSpaceList != null)
                this.ReplacedDuplicatedNameSpaceList(this, e);
        }

        /// <summary>
        /// Protected method for ReplacedDuplicatedNameSpaceEventHandler event
        /// </summary>
        /// <param name="e">ReplacedDuplicatedNameSpaceEventArgs parameter</param>
        private void OnReplacedDuplicatedNameSpace(ReplacedDuplicatedNameSpaceEventArgs e)
        {
            if (this.ReplacedDuplicatedNameSpace != null)
                this.ReplacedDuplicatedNameSpace(this, e);
        }
        #endregion

        #endregion

        #region IDisposable Methods

        public void Dispose()
        {
            this.xmlRoot = null;
            this.xmlDoc = null;
            this.xmlOriginalDocument = null;
            this.listNameSpaces = null;
        }

        #endregion
    }
}
